package com.piker.pikeropen.server;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.annotations.PrimaryKey;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.datastore.Key;
import com.google.gson.Gson;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class GreetingServiceImpl extends HttpServlet {

	private final String[] kPikerGolfers = new String[] { "John", "Pete", "Jeff", "Dustin", "Jeremy", "Mark", "Ike", "Drew", "Colin", "Curt", "Brian", "Kevin", };

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		if (req.getRequestURI().endsWith("hello.html")) {
			PersistenceManager persistenceManager = PMF.get().getPersistenceManager();
			SportingEventDTO piker = loadPiker(persistenceManager);
			getGolfersScores(req.getParameterValues("player"), buildAllGolfers(persistenceManager, piker.getTournaments()), resp);
			persistenceManager.close();
		} else if (req.getRequestURI().endsWith("resetPiker.html")) {
			initPiker(kPikerGolfers);
		} else if (req.getRequestURI().endsWith("score.html")) {
			saveScores(req, resp);
		} else if (req.getRequestURI().endsWith("reviseScore.html")) {
			String player = req.getParameter("player");
			String hole = req.getParameter("hole");
			String score = req.getParameter("score");
			PersistenceManager persistenceManager = PMF.get().getPersistenceManager();
			SportingEventDTO piker = loadPiker(persistenceManager);
			getGolfersScores(req.getParameterValues("player"), buildAllGolfers(persistenceManager, piker.getTournaments()), resp);
			persistenceManager.close();
			reviseScore(player, hole, score, resp);
		}
	}

	private void reviseScore(String player, String holeString, String shotsString, HttpServletResponse resp) throws IOException {
		PersistenceManager persistenceManager = PMF.get().getPersistenceManager();
		SportingEventDTO sportingEventDTO = loadPiker(persistenceManager);
		List<TournamentDTO> tournaments = sportingEventDTO.getTournaments();
		List<GolferDTO> allGolferDTOs = new ArrayList<GolferDTO>();
		for (TournamentDTO tournamentDTO : tournaments) {
			GolferDTO golferDTO = loadJDOFK(GolferDTO.class, persistenceManager, tournamentDTO.getGolfer());
			allGolferDTOs.add(golferDTO);
			if (golferDTO.getName().equals(player)) {
				List<RoundDTO> rounds = tournamentDTO.getRounds();
				int shots = Integer.parseInt(shotsString);
				int hole = Integer.parseInt(holeString) - 1;
				int round = hole / 18;
				int roundHole = hole % 18;
				RoundDTO roundDTO = rounds.get(round);
				List<ScoredHoleDTO> scores = roundDTO.getScores();
				ScoredHoleDTO scoredHoleDTO = scores.get(roundHole);
				scoredHoleDTO.setShots(shots);
				break;
			}
		}

		persistenceManager.makePersistent(sportingEventDTO);
		persistenceManager.close();
	}

	private void saveScores(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		PersistenceManager persistenceManager = PMF.get().getPersistenceManager();
		SportingEventDTO sportingEventDTO = loadPiker(persistenceManager);
		CourseDTO scheduleOfPlay = loadJDOFK(CourseDTO.class, persistenceManager, sportingEventDTO.getScheduleOfPlay());
		List<TournamentDTO> tournaments = sportingEventDTO.getTournaments();
		List<String> playersInGroup = new ArrayList<String>();
		List<GolferDTO> allGolferDTOs = new ArrayList<GolferDTO>();
		for (TournamentDTO tournamentDTO : tournaments) {
			GolferDTO golferDTO = loadJDOFK(GolferDTO.class, persistenceManager, tournamentDTO.getGolfer());
			allGolferDTOs.add(golferDTO);
			String value = req.getParameter(golferDTO.getName());
			if (value != null) {
				int score = Integer.parseInt(value);
				playersInGroup.add(golferDTO.getName());
				List<RoundDTO> rounds = tournamentDTO.getRounds();
				if (rounds == null || rounds.size() <= 0) {
					rounds.add(new RoundDTO(null, new ArrayList<ScoredHoleDTO>()));
				}
				for (RoundDTO roundDTO : rounds) {
					List<ScoredHoleDTO> scores = roundDTO.getScores();
					List<HoleDTO> holes = scheduleOfPlay.getHoles();
					if (scores.size() < holes.size()) {
						HoleDTO holeDTO = holes.get(scores.size());
						scores.add(new ScoredHoleDTO(null, golferDTO.getKey(), holeDTO.getKey(), score));
						break;
					}
				}
			}
		}

		AllGolfers golferScoreCards = buildAllGolfers(persistenceManager, tournaments);
		persistenceManager.makePersistent(sportingEventDTO);
		persistenceManager.close();
		getGolfersScores(playersInGroup.toArray(new String[playersInGroup.size()]), golferScoreCards, resp);
	}

	private <T> T loadJDOFK(Class<T> keyClass, PersistenceManager persistenceManager, Key scheduleOfPlay) {
		if (scheduleOfPlay != null) {
			Query query = persistenceManager.newQuery(keyClass);
			Field[] fields = keyClass.getDeclaredFields();
			for (Field field : fields) {
				if (field.isAnnotationPresent(PrimaryKey.class)) {
					query.setFilter(field.getName() + " == fkParam");
					query.declareParameters(Key.class.getName() + " fkParam");
					List<T> fkValue = (List<T>) query.execute(scheduleOfPlay);
					if (fkValue.size() < 1)
						throw new JDOObjectNotFoundException("Couldn't find this FK: not in DB");
					return fkValue.get(0);
				}
			}
			throw new JDOObjectNotFoundException("Couldn't find this FK: couldn't find PK in Class Definition");
		}
		return null;
	}

	private SportingEventDTO loadPiker(PersistenceManager persistenceManager) {
		Query query = persistenceManager.newQuery(SportingEventDTO.class);
		// query.setOrdering("mNumber");
		List<SportingEventDTO> courseDTOs = (List<SportingEventDTO>) query.execute();
		SportingEventDTO sportingEventDTO = courseDTOs.get(0);
		return sportingEventDTO;
	}

	private void initPiker(String[] pikerGolferNames) {
		List<GolferDTO> golferDTOs = new ArrayList<GolferDTO>();
		for (String pikerGolferName : pikerGolferNames) {
			GolferDTO golferDTO = new GolferDTO(null, pikerGolferName);
			golferDTOs.add(golferDTO);
		}
		PersistenceManager persistenceManager = PMF.get().getPersistenceManager();
		RecuringSportingEventDTO recuringSportingEventDTO = new RecuringSportingEventDTO(null, "Piker Open", golferDTOs);
		persistenceManager.makePersistent(recuringSportingEventDTO);

		List<TournamentDTO> tournamentDTOs = new ArrayList<TournamentDTO>();
		for (GolferDTO golferDTO : golferDTOs) {
			TournamentDTO tournamentDTO = new TournamentDTO(null, null, golferDTO.getKey());
			tournamentDTOs.add(tournamentDTO);
		}
		CourseDTO courseDTO = buildCourseDTO(createTimberRidge());
		persistenceManager.makePersistent(courseDTO);

		SportingEventDTO sportingEventDTO = new SportingEventDTO(null, recuringSportingEventDTO.getKey(), "Piker Open '11", courseDTO.getKey(), tournamentDTOs);

		persistenceManager.makePersistent(sportingEventDTO);
		persistenceManager.close();
	}

	private CourseDTO buildCourseDTO(Course course) {
		Hole[] holes = course.getHoles();
		List<HoleDTO> holeDTOs = new ArrayList<HoleDTO>();
		for (Hole hole : holes) {
			holeDTOs.add(new HoleDTO(null, hole.getNumber(), hole.getYardage(), hole.getHandicap(), hole.getPar()));
		}
		CourseDTO courseDTO = new CourseDTO(null, course.getName(), holeDTOs);
		return courseDTO;
	}

	private void getGolfersScores(String[] playersInGroup, AllGolfers allGolfers, HttpServletResponse resp) throws IOException {
		if (playersInGroup == null) {
			playersInGroup = new String[0];
		}
		Arrays.sort(playersInGroup);
		Golfer[] golfers = allGolfers.getGolfers();

		for (Golfer golfer : golfers) {
			if (Arrays.binarySearch(playersInGroup, golfer.getName()) >= 0)
				golfer.setHighlighted(true);
			else
				golfer.setHighlighted(false);
		}
		Course timberRidge = createTimberRidge();
		int maxHole = 0;
		for (Golfer golfer : golfers) {
			if (golfer.isHighlighted()) {
				maxHole = Math.max(golfer.getHole(), maxHole);
			}
		}
		setScoresAdjustedToPar(allGolfers, timberRidge, ScoreDisplay.PAR);
		Arrays.sort(golfers, new Comparator<Golfer>() {
			@Override
			public int compare(Golfer o1, Golfer o2) {
				return o1.getScoreAdjustedToPar() - o2.getScoreAdjustedToPar();
			}
		});

		Hole hole = timberRidge.getHoles()[maxHole % 18];// TODO it would be nice to express the hole par adjusted for 'piker handicap'
		Update update = new Update(timberRidge.mName, maxHole, hole, golfers);
		String json = new Gson().toJson(update);
		// resp.setContentType("text/ascii");
		resp.getOutputStream().print(json);
		resp.getOutputStream().flush();
	}

	private AllGolfers buildAllGolfers(PersistenceManager persistenceManager, List<TournamentDTO> tournaments) {
		List<Golfer> golferList = new ArrayList<GreetingServiceImpl.Golfer>();
		if (tournaments != null) {
			for (TournamentDTO tournamentDTO : tournaments) {
				GolferDTO golferDTO = loadJDOFK(GolferDTO.class, persistenceManager, tournamentDTO.getGolfer());
				int score = calculateShots(tournamentDTO);
				int hole = calculateHole(tournamentDTO);
				Integer[] scoredHoles = calculateScoredHoles(tournamentDTO);
				golferList.add(new Golfer(golferDTO.getName(), false, score, hole, scoredHoles));
			}
		}
		AllGolfers golfers = new AllGolfers(golferList.toArray(new Golfer[golferList.size()]));
		return golfers;
	}

	private int calculateHole(TournamentDTO tournamentDTO) {
		int hole = 0;
		List<RoundDTO> rounds = tournamentDTO.getRounds();
		for (RoundDTO roundDTO : rounds) {
			hole += roundDTO.getScores().size();
		}
		return hole;
	}

	private int calculateShots(TournamentDTO tournamentDTO) {
		int shots = 0;
		List<RoundDTO> rounds = tournamentDTO.getRounds();
		for (RoundDTO roundDTO : rounds) {
			List<ScoredHoleDTO> scores = roundDTO.getScores();
			for (ScoredHoleDTO scoredHoleDTO : scores) {
				shots += scoredHoleDTO.getShots();
			}
		}
		return shots;
	}

	private Integer[] calculateScoredHoles(TournamentDTO tournamentDTO) {
		List<RoundDTO> rounds = tournamentDTO.getRounds();
		List<Integer> scoredHoles = new ArrayList<Integer>();
		for (RoundDTO roundDTO : rounds) {
			List<ScoredHoleDTO> scores = roundDTO.getScores();
			for (ScoredHoleDTO scoredHoleDTO : scores) {
				scoredHoles.add(scoredHoleDTO.getShots());
			}
		}
		return scoredHoles.toArray(new Integer[scoredHoles.size()]);
	}

	private void setScoresAdjustedToPar(AllGolfers allGolfers, Course course, ScoreDisplay scoreDisplay) {
		Golfer[] golfers = allGolfers.getGolfers();
		for (Golfer golfer : golfers) {
			int finishedHole = golfer.getHole();
			int par = 0;
			for (int i = 0; i < finishedHole; i++) {
				par += course.getHoles()[i].getPar();
			}
			int scoreDisplayInt = golfer.getScore() - par;
			if (scoreDisplay.equals(ScoreDisplay.PIKER_PAR)) {
				final int pikerHandicap = 25;
				for (int i = 0; i < finishedHole; i++) {
					int holeHandicap = course.getHoles()[i].getHandicap();
					int handicapAddition = pikerHandicap / 18 + ((pikerHandicap % 18 >= holeHandicap) ? 1 : 0);
					par += handicapAddition;
				}
				scoreDisplayInt = golfer.getScore() - par;
				String scoreDisplayString;
				if (scoreDisplayInt == 0) {
					scoreDisplayString = "E";
				} else if (scoreDisplayInt > 0) {
					scoreDisplayString = "+" + scoreDisplayInt;
				} else
					scoreDisplayString = "" + scoreDisplayInt;
				golfer.setScoreDisplay(scoreDisplayString);
			} else if (scoreDisplay.equals(ScoreDisplay.PAR)) {
				String scoreDisplayString;
				if (scoreDisplayInt == 0) {
					scoreDisplayString = "E";
				} else if (scoreDisplayInt > 0) {
					scoreDisplayString = "+" + scoreDisplayInt;
				} else
					scoreDisplayString = "" + scoreDisplayInt;
				golfer.setScoreDisplay(scoreDisplayString);
			} else if (scoreDisplay.equals(ScoreDisplay.SHOTS)) {
				String scoreDisplayString;
				if (scoreDisplayInt == 0) {
					scoreDisplayString = "E";
				} else if (scoreDisplayInt > 0) {
					scoreDisplayString = "+" + scoreDisplayInt;
				} else
					scoreDisplayString = "" + scoreDisplayInt;
				golfer.setScoreDisplay("" + golfer.getScore() + " (" + scoreDisplayString + ")");
			}
			golfer.setScoreAdjustedToPar(scoreDisplayInt);
		}
	}

	private Course createTimberRidge() {
		Hole[] holes = new Hole[] { new Hole(1, 5, 465, 15), new Hole(2, 4, 335, 9), new Hole(3, 3, 130, 13), new Hole(4, 5, 490, 7), new Hole(5, 4, 310, 11),
				new Hole(6, 4, 360, 3), new Hole(7, 4, 380, 1), new Hole(8, 3, 110, 17), new Hole(9, 4, 340, 5), new Hole(10, 4, 340, 6),
				new Hole(11, 5, 490, 16), new Hole(12, 3, 150, 18), new Hole(13, 4, 376, 4), new Hole(14, 3, 140, 14), new Hole(15, 5, 445, 10),
				new Hole(16, 4, 380, 2), new Hole(17, 3, 130, 12), new Hole(18, 4, 340, 8), };
		return new Course("TimberRidge", holes);
	}

	private class AllGolfers {
		private final Golfer[] mGolfers;

		public AllGolfers(Golfer[] golfers) {
			mGolfers = golfers;
		}

		public Golfer[] getGolfers() {
			return mGolfers;
		}
	}

	private class Golfer {
		private final String mName;
		private boolean mHighlighted;
		private int mScore;
		private int mHole;
		private String mScoreDisplay;
		private int mScoreAdjustedToPar;
		private Integer[] mScoredHoles;

		public Golfer(String name, boolean highlighted, int score, int hole, Integer[] scoredHoles) {
			mName = name;
			mHighlighted = highlighted;
			mScore = score;
			mHole = hole;
			setScoredHoles(scoredHoles);
			mScoreDisplay = "" + score;
		}

		public String getName() {
			return mName;
		}

		public boolean isHighlighted() {
			return mHighlighted;
		}

		public int getScore() {
			return mScore;
		}

		public int getHole() {
			return mHole;
		}

		public void setHighlighted(boolean highlighted) {
			mHighlighted = highlighted;
		}

		public void setScoreAdjustedToPar(int scoreAdjustedToPar) {
			mScoreAdjustedToPar = scoreAdjustedToPar;
		}

		public int getScoreAdjustedToPar() {
			return mScoreAdjustedToPar;
		}

		public void setScore(int score) {
			mScore = score;
		}

		public void setHole(int hole) {
			mHole = hole;
		}

		public String getScoreDisplay() {
			return mScoreDisplay;
		}

		public void setScoreDisplay(String scoreDisplay) {
			mScoreDisplay = scoreDisplay;
		}

		@Override
		public String toString() {
			return "Golfer [mName=" + mName + ", mHighlighted=" + mHighlighted + ", mScore=" + mScore + ", mHole=" + mHole + ", mScoreDisplay=" + mScoreDisplay
					+ ", mScoreAdjustedToPar=" + mScoreAdjustedToPar + "]";
		}

		public Integer[] getScoredHoles() {
			return mScoredHoles;
		}

		public void setScoredHoles(Integer[] scoredHoles) {
			mScoredHoles = scoredHoles;
		}
	}

	private class Course {
		private final String mName;
		private final Hole[] mHoles;

		public Course(String name, Hole[] holes) {
			mName = name;
			mHoles = holes;
		}

		public String getName() {
			return mName;
		}

		public Hole[] getHoles() {
			return mHoles;
		}
	}

	private class Hole {
		private final int mYardage;
		private final int mHandicap;
		private final int mPar;
		private final int mNumber;

		public Hole(int number, int par, int yardage, int handicap) {
			mNumber = number;
			mPar = par;
			mYardage = yardage;
			mHandicap = handicap;
		}

		public int getYardage() {
			return mYardage;
		}

		public int getHandicap() {
			return mHandicap;
		}

		public int getPar() {
			return mPar;
		}

		public int getNumber() {
			return mNumber;
		}
	}

	private class Update {
		private Hole mHole;
		private Golfer[] mGolfers;
		private final int mHoleNumber;
		private final String mCourseName;

		public Update(String courseName, int holeNumber, Hole hole, Golfer[] golfers) {
			super();
			mCourseName = courseName;
			mHoleNumber = holeNumber;
			mHole = hole;
			mGolfers = golfers;
		}

		public Hole getHole() {
			return mHole;
		}

		public void setHole(Hole hole) {
			mHole = hole;
		}

		public Golfer[] getGolfers() {
			return mGolfers;
		}

		public void setGolfers(Golfer[] golfers) {
			mGolfers = golfers;
		}

		public int getHoleNumber() {
			return mHoleNumber;
		}
	}

	private enum ScoreDisplay {
		SHOTS, PAR, PIKER_PAR
	}
}
